/**    
 * 文件名：VisualWords.java    
 *    
 * 版本信息：    
 * 日期：2014年3月14日    
 * xyj 足下 xyj 2014     
 * 版权所有    
 *    
 */
package learn.visual.words;

import static com.googlecode.javacpp.Loader.sizeof;
import static com.googlecode.javacv.cpp.opencv_core.CV_32FC1;
import static com.googlecode.javacv.cpp.opencv_core.CV_32S;
import static com.googlecode.javacv.cpp.opencv_core.CV_32SC1;
import static com.googlecode.javacv.cpp.opencv_core.CV_64FC1;
import static com.googlecode.javacv.cpp.opencv_core.CV_AUTOSTEP;
import static com.googlecode.javacv.cpp.opencv_core.CV_NEXT_SEQ_ELEM;
import static com.googlecode.javacv.cpp.opencv_core.CV_TERMCRIT_EPS;
import static com.googlecode.javacv.cpp.opencv_core.CV_TERMCRIT_ITER;
import static com.googlecode.javacv.cpp.opencv_core.cvClearSeq;
import static com.googlecode.javacv.cpp.opencv_core.cvCreateMat;
import static com.googlecode.javacv.cpp.opencv_core.cvCreateMemStorage;
import static com.googlecode.javacv.cpp.opencv_core.cvGetSeqElem;
import static com.googlecode.javacv.cpp.opencv_core.cvInitMatHeader;
import static com.googlecode.javacv.cpp.opencv_core.cvKMeans2;
import static com.googlecode.javacv.cpp.opencv_core.cvReleaseMat;
import static com.googlecode.javacv.cpp.opencv_core.cvReleaseMemStorage;
import static com.googlecode.javacv.cpp.opencv_core.cvStartReadSeq;
import static com.googlecode.javacv.cpp.opencv_core.cvTermCriteria;
import static com.googlecode.javacv.cpp.opencv_highgui.CV_LOAD_IMAGE_GRAYSCALE;
import static com.googlecode.javacv.cpp.opencv_highgui.cvLoadImage;
import static com.googlecode.javacv.cpp.opencv_legacy.cvCreateKDTree;
import static com.googlecode.javacv.cpp.opencv_legacy.cvExtractSURF;
import static com.googlecode.javacv.cpp.opencv_legacy.cvFindFeatures;
import static com.googlecode.javacv.cpp.opencv_legacy.cvReleaseFeatureTree;
import static com.googlecode.javacv.cpp.opencv_legacy.cvSURFParams;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.io.FileUtils;

import com.googlecode.javacpp.BytePointer;
import com.googlecode.javacpp.FloatPointer;
import com.googlecode.javacpp.Pointer;
import com.googlecode.javacv.cpp.opencv_core.CvMat;
import com.googlecode.javacv.cpp.opencv_core.CvMemStorage;
import com.googlecode.javacv.cpp.opencv_core.CvRNG;
import com.googlecode.javacv.cpp.opencv_core.CvSeq;
import com.googlecode.javacv.cpp.opencv_core.CvSeqReader;
import com.googlecode.javacv.cpp.opencv_core.IplImage;
import com.googlecode.javacv.cpp.opencv_features2d;
import com.googlecode.javacv.cpp.opencv_imgproc.CvFeatureTree;
import com.googlecode.javacv.cpp.opencv_legacy.CvSURFParams;
import com.googlecode.javacv.cpp.opencv_nonfree;

/**
 * @项目名称：opencv-test
 * @类名称：VisualWords
 * @类描述：
 * @创建人：zhuyi
 * @创建时间：2014年3月14日 下午5:31:10
 * @修改人：zhuyi
 * @修改时间：2014年3月14日 下午5:31:10
 * @修改备注：
 * @version
 * 
 */
public class VisualWords {

    private static final String IMAGE_DIR = "caltech10";

    private static final int DIM = 128;

    private static int SURF_PARAM = 400;

    // cluster数 = visual words的维度数
    private static int MAX_CLUSTER = 500;

    public static void main(String[] args) throws Exception {
        int ret;

        // 从IMAGE_DIR的各个图片特征值抽取
        System.out.println("Load Descriptors ...");
        opencv_features2d.initModule_features2d();
        opencv_nonfree.initModule_nonfree();

        CvMat samples = new CvMat();
        List<Float> data = new ArrayList<Float>();
        ret = loadDescriptors(samples, data);

        // 局所特徴量をクラスタリングして各クラスタのセントロイドを計算
        System.out.println(samples.rows());
        System.out.println(samples.cols());

        System.out.println("Clustering ...");
        // 各个sample分配聚类标签
        CvMat labels = cvCreateMat(samples.rows(), 1, CV_32S);
        // 各クラスタの中心（セントロイド） DIM次元ベクトル
        CvMat centroids = cvCreateMat(MAX_CLUSTER, DIM, CV_32FC1);
        cvKMeans2(samples, MAX_CLUSTER, labels, cvTermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 10, 1.0), 1,
                new CvRNG(null), 0, centroids, null);

        // 标签不使用，释放之
        cvReleaseMat(labels);

        // 各个图片Visual Words的直方图转换
        // 各个簇的中心向量，用centroids将Visual Words一个个的组成
        System.out.println("Calc Histograms ...");
        calcHistograms(centroids);

    }

    /**
     * 从图片文件中抽出SURF特征值
     * 
     * @param[in] filename 图片文件名
     * @param[out] imageKeypoints KeyPoints
     * @param[out] imageDescriptors 各KeyPoint的SURF特征值
     * @param[out] storage 内存区域
     * @return int 如果成功，返回0， 失败返回1
     */
    public static int extractSURF(String fileName, CvSeq keypoints, CvSeq descriptors, CvMemStorage storage) {

        // 以灰度方式读取图像
        IplImage img = cvLoadImage(fileName, CV_LOAD_IMAGE_GRAYSCALE);
        if (img == null) {
            throw new UnsupportedOperationException("cannot load image: " + fileName);
        }

        CvSURFParams params = cvSURFParams(SURF_PARAM, 1);
        cvExtractSURF(img, null, keypoints, descriptors, storage, params, 0);

        return 0;
    }

    /**
     * 从IMAGE_DIR抽取出来的所有特征值往矩阵(CvMat)中储存
     * 
     * @param[out] samples 特征值的矩阵
     * @param[out] data samples的数据领域
     * @return 成功返回0、失败返回1
     */
    public static int loadDescriptors(CvMat samples, List<Float> data) {

        // 打开IMAGE_DIR
        File imageDir = new File(IMAGE_DIR);

        if (!imageDir.exists()) {
            throw new UnsupportedOperationException("cannot open directory: " + IMAGE_DIR);
        }

        // 循环IMAGE_DIR下的图像文件名
        File[] images = imageDir.listFiles();
        for (File image : images) {
            String fileName = image.getName();
            String filePath = IMAGE_DIR + "/" + fileName;
            // System.out.println(filePath);

            // SURF抽出
            CvSeq keypoints = new CvSeq();
            CvSeq descriptors = new CvSeq();
            CvMemStorage storage = cvCreateMemStorage(0);
            int ret = extractSURF(filePath, keypoints, descriptors, storage);
            if (ret != 0) {
                throw new UnsupportedOperationException("error in extractSURF");
            }

            // 文件名和特征点数输出
            System.out.println(filePath + "\t" + descriptors.total());

            // 特征值无结构化向data添加
            for (int i = 0; i < descriptors.total(); i++) {
                FloatPointer d = new FloatPointer(cvGetSeqElem(descriptors, i)); // 128次元ベクトル
                for (int j = 0; j < DIM; j++) {
                    data.add(d.get(j));
                }
                // data.add(d.get(i));
                // System.out.println(d.position(j));
                // System.out.println(d.position(j).limit(j +
                // 1).asByteBuffer());
            }

            cvClearSeq(keypoints);
            cvClearSeq(descriptors);
            cvReleaseMemStorage(storage);
        }

        // dataをCvMat形式に変換
        // CvMatはdataを参照するためdataは解放されないので注意
        System.out.println(data.size());
        int rows = data.size() / DIM; // CvMatの行数（=DIM次元特徴ベクトルの本数）
        FloatPointer p = new FloatPointer(data.size());
        float[] _data = new float[data.size()];

        for (int i = 0; i < data.size(); i++) {
            _data[i] = data.get(i).floatValue();
        }
        p.put(_data);
        System.out.println(p.capacity());

        // for (int i = 0; i < p.capacity(); i++) {
        // System.out.println(p.get(i));
        // }
        cvInitMatHeader(samples, rows, DIM, CV_32FC1, p, CV_AUTOSTEP);

        // for (int i = 0; i < samples.rows(); i++) {
        // for (int j = 0; j < samples.cols(); j++) {
        // System.out.println(samples.get(i, j));
        // }
        // }

        return 0;
    }

    /**
     * IMAEG_DIR的所有图片的直方图变化并输出 各画像の各局所特徴量を一番近いVisual Wordsに投票してヒストグラムを作成する
     * 
     * @param[in] visualWords Visual Words
     * @return 成功なら0、失敗なら1
     * @throws Exception
     */
    public static int calcHistograms(CvMat visualWords) throws Exception {

        // 一番近いVisual Wordsを高速検索できるようにvisualWordsをkd-treeでインデキシング
        CvFeatureTree ft = cvCreateKDTree(visualWords);

        // 各画像のヒストグラムを出力するファイルを開く
        File hf = new File("histograms.txt");

        // 打开IMAGE_DIR
        File imageDir = new File(IMAGE_DIR);

        if (!imageDir.exists()) {
            throw new UnsupportedOperationException("cannot open directory: " + IMAGE_DIR);
        }

        // 循环IMAGE_DIR下的图像文件名
        File[] images = imageDir.listFiles();
        for (File image : images) {
            String fileName = image.getName();
            String filePath = IMAGE_DIR + "/" + fileName;
            // System.out.println(filePath);

            // 直方图初始化
            int[] histogram = new int[visualWords.rows()];
            for (int i = 0; i < visualWords.rows(); i++) {
                histogram[i] = 0;
            }

            // SURF抽出
            CvSeq keypoints = new CvSeq();
            CvSeq descriptors = new CvSeq();
            CvMemStorage storage = cvCreateMemStorage(0);
            int ret = extractSURF(filePath, keypoints, descriptors, storage);

            // kd-treeで高速検索できるように特徴ベクトルをCvMatに展開
            CvMat mat = cvCreateMat(descriptors.total(), DIM, CV_32FC1);
            CvSeqReader reader = new CvSeqReader();
            FloatPointer ptr = mat.data_fl();
            cvStartReadSeq(descriptors, reader, 0);
            for (int i = 0; i < descriptors.total(); i++) {
                BytePointer desc = reader.ptr();
                CV_NEXT_SEQ_ELEM(reader.seq().elem_size(), reader);
                Pointer.memcpy(ptr, desc, DIM * sizeof(FloatPointer.class));
                ptr = ptr.position(i * DIM);
            }

            // 各局所特徴点についてもっとも類似したVisual Wordsを見つけて投票
            int k = 1; // 1-NN
            // もっとも近いVisual Wordsのインデックス
            CvMat indices = cvCreateMat(keypoints.total(), k, CV_32SC1);
            // その距離
            CvMat dists = cvCreateMat(keypoints.total(), k, CV_64FC1);
            cvFindFeatures(ft, mat, indices, dists, k, 250);
            for (int i = 0; i < indices.rows(); i++) {
                int idx = (int) indices.get(i, 0);
                System.out.println(idx);
                histogram[idx] += 1;
            }

            // ヒストグラムをファイルに出力
            FileUtils.write(hf, filePath + "\t", true);
            for (int i = 0; i < visualWords.rows(); i++) {
                FileUtils.write(hf, ((float) histogram[i] / (float) descriptors.total()) + "\t", true);
            }
            FileUtils.write(hf, "\n", true);

            // 後始末
            histogram = null;
            cvClearSeq(keypoints);
            cvClearSeq(descriptors);
            cvReleaseMemStorage(storage);
            cvReleaseMat(mat);
            cvReleaseMat(indices);
            cvReleaseMat(dists);

        }

        cvReleaseFeatureTree(ft);

        return 0;
    }
}
